﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Xml;
using System.IO;
using System.Net;

using Microsoft.Win32;
using System.Diagnostics;

using BoxSync.Core;
using BoxSync.Core.Primitives;
using BoxSync.Core.ServiceReference;
using BoxSync.Core.Statuses;


namespace BoxDropbox
{
    class BoxFetcher
    {

     #region Singleton Info
        private static BoxFetcher instance;

        private BoxFetcher()
        {
#if DEBUG_LOG
            Console.WriteLine("Make fetcher");
#endif
            boxManager = new BoxManager(APIKey, Properties.Settings.Default.SOAP_URL, null);  
        }

        public static BoxFetcher Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new BoxFetcher();
                }

                return instance;
            }
        }
        #endregion
  
        private readonly string APIKey = Properties.Settings.Default.api_key;
        public readonly BoxManager boxManager;
        private string _ticket;
        private string _authToken;

        /// <summary>
        /// Starts the authentication process with Box.com.
        /// </summary>
        /// <returns>True on authentication success, false on failure</returns>
        public bool startAuth()
        {
            // Check if auth token and ticket exist, if either of them do not, re-auth
            if (Properties.Settings.Default.auth_token != "" && Properties.Settings.Default.ticket != "")
            {
                // Auth token and ticket are stored - read them and authenticate
                boxManager.AuthenticationToken = Properties.Settings.Default.auth_token;
#if DEBUG_LOG
                Console.WriteLine("Loaded Auth token from file: " + boxManager.AuthenticationToken);
#endif
                return true;
            }

            // Get Ticket
#if DEBUG_LOG
            Console.WriteLine("Getting auth ticket!");
#endif
            GetTicketStatus ts = boxManager.GetTicket(out _ticket);
           
            // Ensure getting ticket was successful
            if (ts != GetTicketStatus.Successful)
            {
                Console.WriteLine("Error getting ticket: " + ts);
                return false;
            }

            Properties.Settings.Default.ticket = _ticket;
            Properties.Settings.Default.Save();

            string redirectURL = Properties.Settings.Default.AUTH_URL + _ticket;

            DialogResult authPrompt = MessageBox.Show("After clicking 'OK' a browser window will open - please authenticate through Box.com.", "BoxDropbox - Box.Com Authentication", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);

            if (authPrompt == DialogResult.OK)
            {
                Process.Start(redirectURL);
                DialogResult authPrompt2 = MessageBox.Show("Did you login successfully?", "BoxDropbox - Authentication Response", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if (authPrompt2 == DialogResult.Yes)
                {
                    finishAuth();
                    return true;
                }
            }
            else
            {
                MessageBox.Show("Please use the settings menu to authorize your account.", "BoxDropbox - Box.com Auth Aborted!", MessageBoxButtons.OK);
                return false;
            }

            Console.WriteLine("SHIT HIT THE FAN!");
            return false;
        }


        /// <summary>
        /// Retrieves the Authentication token - ticket must have been acquired first.
        /// </summary>
        public void finishAuth()
        {
            boxManager.GetAuthenticationToken(_ticket, GetAuthenticationTokenCompleted);
        }

        /// <summary>
        /// Sets Authentication token, saves it to settings.
        /// </summary>
        /// <param name="response"></param>
        public void GetAuthenticationTokenCompleted(GetAuthenticationTokenResponse response)
        {
            _authToken = Properties.Settings.Default.auth_token == "" ? response.AuthenticationToken : Properties.Settings.Default.auth_token;
#if DEBUG_LOG
            Console.WriteLine("Auth Token: " + _authToken);
#endif
            Properties.Settings.Default.authenticated = true;
            Properties.Settings.Default.auth_token = _authToken;
            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Currently prints a list of the folders and files in the user's Box.com account.
        /// </summary>
        /// <param name="folderID">The Box.com folder ID to look in. To check root folder, use -1.</param>
        public void pollForFiles(long folderID, string filePath)
        {
            GetFolderStructureResponse folderResponse;
            RetrieveFolderStructureOptions folderOptions = RetrieveFolderStructureOptions.OneLevel;

            if (folderID == -1)
            {
#if DEBUG_LOG
                Console.WriteLine("BoxFetcher: poll files");
#endif
                folderResponse = boxManager.GetRootFolderStructure(folderOptions);

            }
            else
            {
                folderResponse = boxManager.GetFolderStructure(folderID, folderOptions);
            }


            foreach (BoxSync.Core.Primitives.File f in folderResponse.Folder.Files)
            {
#if DEBUG_LOG
                Console.WriteLine("File: " + f.Name);
#endif
                DownloadDelegate dlDel = new DownloadDelegate(downloadFile);
                IAsyncResult result = dlDel.BeginInvoke(f, filePath,null,null);
                dlDel.EndInvoke(result);
            }
            
            foreach (BoxSync.Core.Primitives.Folder fold in folderResponse.Folder.Folders){
#if DEBUG_LOG
                Console.WriteLine("Folder: " + fold.Name);
                Console.WriteLine("Folder path: " + Properties.Settings.Default.ROOT_DIR + "/" +fold.Name);
#endif
                System.IO.Directory.CreateDirectory(Properties.Settings.Default.ROOT_DIR + "/" + fold.Name);
                pollForFiles(fold.ID,Properties.Settings.Default.ROOT_DIR+ "/" + fold.Name);
            }
        }

        private delegate void DownloadDelegate(BoxSync.Core.Primitives.File file, string outputDir);

        private void downloadFile(BoxSync.Core.Primitives.File file, string outputDir)
        {
            WebRequest request = WebRequest.Create(Properties.Settings.Default.GET_URL + "/" + Properties.Settings.Default.auth_token + "/" + file.ID);
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();

            Stream outputStream = System.IO.File.OpenWrite(outputDir + "/" + file.Name);
            CopyStream(stream, outputStream);
        }

        // copied from StackOverflow
        // http://stackoverflow.com/questions/230128/best-way-to-copy-between-two-stream-instances-c-sharp
        private void CopyStream(Stream input, Stream output)
        {
            byte[] buffer = new byte[32768];
            int read;
            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, read);
            }
        }


        /// <summary>
        /// Helper function to search for a folder
        /// </summary>
        /// <param name="path">Relative path to box folder</param>
        /// <param name="currentFolder">current folder</param>
        /// <returns>Folder object if found, null if not found</returns>
        private BoxSync.Core.Primitives.Folder searchForFolder(string path,BoxSync.Core.Primitives.Folder currentFolder)
        {

#if DEBUG_LOG
            Console.WriteLine("Path: " + path);
            Console.WriteLine("Current Folder: " + currentFolder.Name);
#endif
            if (path.Length == 0)
            {
                return currentFolder;
            }
            string[] pathFolders = path.Split('\\');

            foreach (BoxSync.Core.Primitives.Folder f in currentFolder.Folders)
            {
                if (f.Name.Equals(pathFolders[0]))
                {
                    path = path.Remove(0, f.Name.Length+"\\".Length);

#if DEBUG_LOG
                    Console.WriteLine("Removing folder: " + f.Name + ", now is: " + path);
#endif
                    return searchForFolder(path, f);
                }
            }



            return null;
        }


        public BoxSync.Core.Primitives.Folder getFolderFromPath(string path)
        {
           return searchForFolder(path, boxManager.GetRootFolderStructure(new BoxSync.Core.Primitives.RetrieveFolderStructureOptions()).Folder);
        }

    }
   
}
